package org.andengine.opengl.texture.compressed.pvr;

import org.andengine.opengl.texture.ITextureStateListener;
import org.andengine.opengl.texture.TextureManager;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.IPVRTexturePixelBufferStrategy;
import org.andengine.util.StreamUtils;
import org.andengine.util.adt.array.ArrayUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 14:17:23 - 27.07.2011
 */
public abstract class PVRCCZTexture extends PVRTexture {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private CCZHeader mCCZHeader;

    // ===========================================================
    // Constructors
    // ===========================================================

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pTextureStateListener);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, pTextureStateListener);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pTextureOptions);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, pTextureOptions);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pTextureOptions, pTextureStateListener);
    }

    public PVRCCZTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
        super(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, pTextureOptions, pTextureStateListener);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public final InflaterInputStream getInputStream() throws IOException {
        final InputStream inputStream = this.onGetInputStream();

        this.mCCZHeader = new CCZHeader(StreamUtils.streamToBytes(inputStream, CCZHeader.SIZE));

        return this.mCCZHeader.getCCZCompressionFormat().wrap(inputStream);
    }

    @Override
    public ByteBuffer getPVRTextureBuffer() throws IOException {
        final InputStream inputStream = this.getInputStream();
        try {
            final byte[] data = new byte[this.mCCZHeader.getUncompressedSize()];
            StreamUtils.copy(inputStream, data);
            return ByteBuffer.wrap(data);
        } finally {
            StreamUtils.close(inputStream);
        }
    }

    // ===========================================================
    // Methods
    // ===========================================================

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static enum CCZCompressionFormat {
        // ===========================================================
        // Elements
        // ===========================================================

        ZLIB((short) 0),
        BZIP2((short) 1),
        GZIP((short) 2),
        NONE((short) 3);

        // ===========================================================
        // Constants
        // ===========================================================

        // ===========================================================
        // Fields
        // ===========================================================

        private final short mID;

        // ===========================================================
        // Constructors
        // ===========================================================

        private CCZCompressionFormat(final short pID) {
            this.mID = pID;
        }

        public static CCZCompressionFormat fromID(final short pID) {
            final CCZCompressionFormat[] cczCompressionFormats = CCZCompressionFormat.values();
            final int cczCompressionFormatCount = cczCompressionFormats.length;
            for (int i = 0; i < cczCompressionFormatCount; i++) {
                final CCZCompressionFormat cczCompressionFormat = cczCompressionFormats[i];
                if (cczCompressionFormat.mID == pID) {
                    return cczCompressionFormat;
                }
            }
            throw new IllegalArgumentException("Unexpected " + CCZCompressionFormat.class.getSimpleName() + "-ID: '" + pID + "'.");
        }

        public InflaterInputStream wrap(final InputStream pInputStream) throws IOException {
            switch (this) {
                case GZIP:
                    return new GZIPInputStream(pInputStream);
                case ZLIB:
                    return new InflaterInputStream(pInputStream, new Inflater());
                case NONE:
                case BZIP2:
                default:
                    throw new IllegalArgumentException("Unexpected " + CCZCompressionFormat.class.getSimpleName() + ": '" + this + "'.");
            }
        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }

    public static class CCZHeader {
        // ===========================================================
        // Constants
        // ===========================================================

        public static final int SIZE = 16;
        static final byte[] MAGIC_IDENTIFIER = {
                (byte) 'C',
                (byte) 'C',
                (byte) 'Z',
                (byte) '!'
        };

        // ===========================================================
        // Fields
        // ===========================================================
        private final ByteBuffer mDataByteBuffer;
        private final CCZCompressionFormat mCCZCompressionFormat;

        // ===========================================================
        // Constructors
        // ===========================================================

        public CCZHeader(final byte[] pData) {
            this.mDataByteBuffer = ByteBuffer.wrap(pData);
            this.mDataByteBuffer.rewind();
            this.mDataByteBuffer.order(ByteOrder.BIG_ENDIAN);

			/* Check magic bytes. */
            if (!ArrayUtils.equals(pData, 0, CCZHeader.MAGIC_IDENTIFIER, 0, CCZHeader.MAGIC_IDENTIFIER.length)) {
                throw new IllegalArgumentException("Invalid " + this.getClass().getSimpleName() + "!");
            }

            // TODO Check the version?

            this.mCCZCompressionFormat = CCZCompressionFormat.fromID(this.getCCZCompressionFormatID());
        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        private short getCCZCompressionFormatID() {
            return this.mDataByteBuffer.getShort(4);
        }

        public CCZCompressionFormat getCCZCompressionFormat() {
            return this.mCCZCompressionFormat;
        }

        public short getVersion() {
            return this.mDataByteBuffer.getShort(6);
        }

        public int getUserdata() {
            return this.mDataByteBuffer.getInt(8);
        }

        public int getUncompressedSize() {
            return this.mDataByteBuffer.getInt(12);
        }

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }
}
